﻿using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using ProgramMain.Database;
using ProgramMain.Database.SimpleOpticNetDBTableAdapters;
using ProgramMain.Google;
using HashItem = System.Collections.Generic.KeyValuePair<double, int>;
using NearestSet = System.Collections.Generic.HashSet<System.Collections.Generic.KeyValuePair<double, int>>;

namespace ProgramMain.Layers
{
    public class MeasurementLayer : GraphicLayer
    {
        private MeasureTree RMeasureTree = null;

        private readonly ReaderWriterLockSlim lockCTR = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

        protected class NetWorkerEvent : WorkerEvent
        {
            public NetWorkerEvent(WorkerEventType pEventType, EventPriorityType PriorityType)
                : base(pEventType, true, PriorityType)
            {
            }

            override public int CompareTo(object obj)
            {
                int res = base.CompareTo(obj);
                //if (res == 0)
                //{
                //    if (obj is NetWorkerEvent)
                //    {
                //        res = ((NetWorkerEvent)obj).VertexID.CompareTo(this.VertexID);
                //    }
                //    else
                //    {
                //        res = -1;
                //    }
                //}
                return res;
            }
        }
        public double CoordinateTolerance { get; private set; }

       // private static Size[] _HalfVertexSize = { new Size(4, 4), new Size(8, 8), new Size(12, 12) };
        public Size HalfVertexSize
        {
            get
            {
                if (Level <= 14)
                    return NetLayer._HalfVertexSize[0];
                else if (Level > 16)
                    return NetLayer._HalfVertexSize[2];
                else
                    return NetLayer._HalfVertexSize[1];
            }
        }
        
        private readonly ReaderWriterLockSlim lockVR = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

        public MeasurementLayer(int pWidth, int pHeight, Coordinate pCenterCoordinate, int pLevel, Control pDelegateControl, PixelFormat pPIFormat)
            : base(pWidth, pHeight, pCenterCoordinate, pLevel, pDelegateControl, pPIFormat)
        {
            RMeasureTree = new MeasureTree();
        }

        protected override bool DispatchThreadEvents(WorkerEvent WorkerEvent)
        {
            bool res = base.DispatchThreadEvents(WorkerEvent);

            if (!res && WorkerEvent is NetWorkerEvent)
            {
                switch (WorkerEvent.EventType)
                {
                    case WorkerEventType.ReloadData:
                        {
                          //  ClearMeasureData();
                            return true;
                        }
                }
            }

            return res;
        }

        public int RulerType { get; set; }
        public void ReloadData()
        {
            PutWorkerThreadEvent(new NetWorkerEvent(WorkerEventType.ReloadData, EventPriorityType.Normal));
        }

        override protected void DrawLayer(Rectangle ClipRectangle)
        {
            try
            {
                SwapDrawBuffer();

                //рисовать все объекты на карте независимо от ClipRectangle, т.к. FillTransparent!
                FillTransparent();

                GoogleRectangle LocalScreenView = (GoogleRectangle)GoogleScreenView.Clone();

                RedrawMeasures(LocalScreenView);
                RedrawMeters();
            }
            finally
            {
                SwapDrawBuffer();
            }
            FireIvalidateLayer(ClipRectangle);
        }

        public void ClearMeasureData()
        {
            RMeasureTree.ClearMeasureData();
            oldCoordinate = null;
            Update(new Rectangle(0, 0, Width, Height));
        }

        private Size GetMarkSize(double angle, double len, double width)
        {
            double rule = width > 0 ? angle - Math.PI/2 : angle + Math.PI/2;
            return new Size((int)(len * Math.Sin(rule)), (int)(len * Math.Cos(rule)));
        }

        public CoordinateRectangle GetNearestRectangle(Coordinate point)
        {
            CoordinateRectangle res = null;
            double l = int.MaxValue;
            GoogleRectangle LocalScreenView = (GoogleRectangle)GoogleScreenView.Clone();
            HashSet<IGoogleTreeNode> query = RMeasureTree.Query(LocalScreenView);
            foreach (IGoogleTreeNode INode in query)
            {
                SimpleOpticNetDB.MeasuresRow row = (SimpleOpticNetDB.MeasuresRow)INode.Row;

                CoordinateRectangle tunnelRect = new CoordinateRectangle(
                    row.Longitude1,
                    row.Latitude1,
                    row.Longitude2,
                    row.Latitude2);
               double len = EarthUtilities.GetDistance(tunnelRect, point);
                if (len <l)
                {
                    l = len;
                    res = tunnelRect;
                }
            }
            return res;
        }
        private bool RedrawMeasures(GoogleRectangle LocalScreenView)
        {
            try
            {
                lockCTR.EnterReadLock();

                if (RMeasureTree.ItemCount == 0) return true;

                double Ruler = RMeasureTree.Ruler;
                Size cableTunnelThickness = new Size(6, 6);

                HashSet<IGoogleTreeNode> res = RMeasureTree.Query(LocalScreenView);
                foreach (IGoogleTreeNode INode in res)
                {
                    SimpleOpticNetDB.MeasuresRow row = (SimpleOpticNetDB.MeasuresRow) INode.Row;

                    CoordinateRectangle tunnelRect = new CoordinateRectangle(
                        row.Longitude1,
                        row.Latitude1,
                        row.Longitude2,
                        row.Latitude2);

                    Rectangle rect = tunnelRect.GetScreenRect(LocalScreenView);

                    if (rect.Width != 0 || rect.Height != 0)
                    {
                        Color color = Color.DarkViolet;
                        DrawLine(rect, 2, color);
                       
                        if (RulerType == 1)
                        {
                            Coordinate coordinate = tunnelRect.LineMiddlePoint();
                            if (CoordinateScreenView.PointContains(coordinate) != InterseptResult.None)
                            {
                                Point point = coordinate.GetScreenPoint(LocalScreenView);
                                string Caption = Convert.ToString((int) row.Length) + "м";
                                DrawString(Caption, cableTunnelThickness.Width*2, Point.Add(point, cableTunnelThickness));
                            }
                        }
                        else if (RulerType == 2)
                        {
                            int mark = 0;
                            double a = Math.Atan2(rect.Width, rect.Height);
                            double len = rect.Height / Math.Cos(a);// Math.Pow(rect.Width * rect.Width + rect.Height * rect.Height, 0.5);
                            double coordinatLen = tunnelRect.LineLength();
                            double scale = coordinatLen / len;
                            while (mark < coordinatLen)
                            {
                                int ost;
                                Math.DivRem(mark, 10, out ost);
                                int marklen = ost == 0 ? 35 : 10;

                                Size s = new Size((int)(mark / scale * Math.Sin(a)), (int)(mark / scale * Math.Cos(a)));
                                Point markPoint = rect.Location + s;
                                Size markSize = GetMarkSize(a, marklen, rect.Width);
                                Rectangle r = new Rectangle(markPoint.X, markPoint.Y, markSize.Width,
                                                            markSize.Height);
                                DrawLine(r, 2, Color.Red);
                                if (ost == 0)
                                {
                                    DrawString(mark.ToString(), 8, markPoint + markSize);
                                }
                                mark += 1;
                            }
                        }
                       
                    }
                }
            }
            finally
            {
                lockCTR.ExitReadLock();
            }
            return true;
        }

        private void RedrawMeters()
        {
            
        }

        private Coordinate oldCoordinate = null;

        public void ClearOldCoordinate()
        {
            oldCoordinate = null;
            RMeasureTree.Reload();
        }
        public SimpleOpticNetDB.MeasuresRow FindFirst(int ID)
        {
            if (oldCoordinate != null) return null;
            return RMeasureTree.FindMeasure(ID);
        }
        public void EditMeasure(Coordinate point)
        {
            try
            {
                lockCTR.EnterWriteLock();
                if(oldCoordinate !=null && point != null)
                    RMeasureTree.EditMeasure(oldCoordinate,point);
                oldCoordinate = point;
            }
            finally
            {
                lockCTR.ExitWriteLock();
            }
            Update(new Rectangle(0, 0, Width, Height));
        }

        public void AddMeasure(Coordinate point)
        {
            try
            {
                lockCTR.EnterWriteLock();

                RMeasureTree.AddMeasure(point);
            }
            finally
            {
                lockCTR.ExitWriteLock();
            }
            Update(new Rectangle(0, 0, Width, Height));
        }

        override protected void TranslateCoords()
        {
            base.TranslateCoords();

            // Определяем погрешность для попадания в объекты
            CoordinateTolerance = CenterCoordinate.Distance(
                CenterCoordinate + new GoogleCoordinate(HalfVertexSize.Width, 0, Level));
        }

        public HashItem[] FindMeasurePoint(Coordinate pt)
        {
            NearestSet list = new NearestSet();
            try
            {
                lockVR.EnterReadLock();

                if (RMeasureTree.ItemCount == 0)
                    return list.ToArray();

                HashSet<IGoogleTreeNode> res = RMeasureTree.Distance(pt, CoordinateTolerance);
                foreach (IGoogleTreeNode INode in res)
                {
                    SimpleOpticNetDB.MeasuresRow row = (SimpleOpticNetDB.MeasuresRow)INode.Row;

                    Coordinate coordinate = new Coordinate(row.Longitude1, row.Latitude1);

                    double distance = coordinate.Distance(pt);
                    
                    list.Add(new HashItem(distance, row.ID));
                }
            }
            finally
            {
                lockVR.ExitReadLock();
            }
            return list.OrderBy(item => item.Key).ToArray();
        }
    }
}
